﻿#region "License Agreement"
/* Skimpt, an open source screenshot utility.
      Copyright (C) <year>  <name of author>

      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      this program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>. */
#endregion
using System;
using System.Net;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;


/// <summary>
/// Allow the transfer of data files using the W3C's specification
/// for HTTP multipart form data. Microsoft's version has a bug
/// where it does not format the ending boundary correctly.
/// Written by: gregoryp@norvanco.com
/// </summary>
public class MultipartForm
{

    public string InputBoxName;

    /// <summary> 
    /// Holds any form fields and values that you 
    /// wish to transfer with your data. 
    /// </summary> 
    private Hashtable coFormFields;
    /// <summary> 
    /// Used mainly to avoid passing parameters to other routines. 
    /// Could have been local to sendFile(). 
    /// </summary> 
    protected HttpWebRequest coRequest;
    /// <summary> 
    /// Used if we are testing and want to output the raw 
    /// request, minus http headers, out to a file. 
    /// </summary> 
    System.IO.Stream coFileStream;
    /// <summary> 
    /// Difined to build the form field data that is being 
    /// passed along with the request. 
    /// </summary> 
    static string CONTENT_DISP = "Content-Disposition: form-data; name=";
    /// <summary> 
    /// Allows you to specify the specific version of HTTP to use for uploads. 
    /// The dot NET stuff currently does not allow you to remove the continue-100 header 
    /// from 1.1 and 1.0 currently has a bug in it where it adds the continue-100. MS 
    /// has sent a patch to remove the continue-100 in HTTP 1.0. 
    /// </summary> 
    public Version TransferHttpVersion
    { get { return coHttpVersion; } set { coHttpVersion = value; } }
    Version coHttpVersion;

    /// <summary> 
    /// Used to change the content type of the file being sent. 
    /// Currently defaults to: text/xml. Other options are 
    /// text/plain or binary 
    /// </summary> 
    public string FileContentType
    { get { return coFileContentType; } set { coFileContentType = value; } }
    string coFileContentType;

    /// <summary> 
    /// Initialize our class for use to send data files. 
    /// </summary> 
    /// <param name="url">The web address of the recipient of the data transfer.</param> 
    public MultipartForm(string url)
    {
        URL = url;
        coFormFields = new Hashtable();
        ResponseText = new StringBuilder();
        BufferSize = 1024 * 10;
        BeginBoundary = "ou812--------------8c405ee4e38917c";
        TransferHttpVersion = HttpVersion.Version11;
        FileContentType = "text/xml";
    }
    //---------- BEGIN PROPERTIES SECTION ---------- 
    string _BeginBoundary;
    /// <summary> 
    /// The string that defines the begining boundary of 
    /// our multipart transfer as defined in the w3c specs. 
    /// This method also sets the Content and Ending 
    /// boundaries as defined by the w3c specs. 
    /// </summary> 
    public string BeginBoundary
    {
        get { return _BeginBoundary; }
        set
        {
            _BeginBoundary = value;
            ContentBoundary = "--" + BeginBoundary;
            EndingBoundary = ContentBoundary + "--";
        }
    }
    /// <summary> 
    /// The string that defines the content boundary of 
    /// our multipart transfer as defined in the w3c specs. 
    /// </summary> 
    protected string ContentBoundary
    { get { return _ContentBoundary; } set { _ContentBoundary = value; } }
    string _ContentBoundary;
    /// <summary> 
    /// The string that defines the ending boundary of 
    /// our multipart transfer as defined in the w3c specs. 
    /// </summary> 
    protected string EndingBoundary
    { get { return _EndingBoundary; } set { _EndingBoundary = value; } }
    string _EndingBoundary;
    /// <summary> 
    /// The data returned to us after the transfer is completed. 
    /// </summary> 
    public StringBuilder ResponseText
    { get { return _ResponseText; } set { _ResponseText = value; } }
    StringBuilder _ResponseText;
    /// <summary> 
    /// The web address of the recipient of the transfer. 
    /// </summary> 
    public string URL
    { get { return _URL; } set { _URL = value; } }
    string _URL;
    /// <summary> 
    /// Allows us to determine the size of the buffer used 
    /// to send a piece of the file at a time out the IO 
    /// stream. Defaults to 1024 * 10. 
    /// </summary> 
    public int BufferSize
    { get { return _BufferSize; } set { _BufferSize = value; } }
    int _BufferSize;
    //---------- END PROPERTIES SECTION ---------- 
    /// <summary> 
    /// Used to signal we want the output to go to a 
    /// text file verses being transfered to a URL. 
    /// </summary> 
    /// <param name="path"></param> 
    public void setFilename(string path)
    {
        coFileStream = new System.IO.FileStream(path, FileMode.Create, FileAccess.Write);
    }
    /// <summary> 
    /// Allows you to add some additional field data to be 
    /// sent along with the transfer. This is usually used 
    /// for things like userid and password to validate the 
    /// transfer. 
    /// </summary> 
    /// <param name="key">The form field name</param> 
    /// <param name="str">The form field value</param> 
    public void setField(string key, string str)
    {
        coFormFields[key] = str;
    }
    /// <summary> 
    /// Determines if we have a file stream set, and returns either 
    /// the HttpWebRequest stream of the file. 
    /// </summary> 
    /// <returns></returns> 
    public virtual System.IO.Stream getStream()
    {
        System.IO.Stream io;
        if (null == coFileStream)
            io = coRequest.GetRequestStream();
        else
            io = coFileStream;
        return io;
    }
    /// <summary> 
    /// Here we actually make the request to the web server and 
    /// retrieve it's response into a text buffer. 
    /// </summary> 
    public virtual void getResponse()
    {
        if (null == coFileStream)
        {
            System.IO.Stream io;
            WebResponse oResponse;
            try
            {
                oResponse = coRequest.GetResponse();
            }
            catch (WebException web)
            {
                oResponse = web.Response;
            }
            if (null != oResponse)
            {
                io = oResponse.GetResponseStream();
                StreamReader sr = new StreamReader(io);
                string str;
                ResponseText.Length = 0;
                while ((str = sr.ReadLine()) != null)
                    ResponseText.Append(str);
                oResponse.Close();
            }
            else
                throw new Exception("MultipartForm: Error retrieving server response");
        }
    }
    /// <summary> 
    /// Transmits a file to the web server stated in the 
    /// URL property. You may call this several times and it 
    /// will use the values previously set for fields and URL. 
    /// </summary> 
    /// <param name="aFilename">The full path of file being transfered.</param> 
    public void sendFile(string aFilename)
    {
        // The live of this object is only good during 
        // this function. Used mainly to avoid passing 
        // around parameters to other functions. 
        coRequest = (HttpWebRequest)WebRequest.Create(URL);
        // Set use HTTP 1.0 or 1.1. 
        coRequest.ProtocolVersion = TransferHttpVersion;
        coRequest.Method = "POST";
        coRequest.ContentType = "multipart/form-data; boundary=" + BeginBoundary;
        coRequest.Headers.Add("Cache-Control", "no-cache");
        coRequest.KeepAlive = true;
        string strFields = getFormfields();
        string strFileHdr = getFileheader(aFilename);
        string strFileTlr = getFiletrailer();
        FileInfo info = new FileInfo(aFilename);
        coRequest.ContentLength = strFields.Length +
          strFileHdr.Length +
          strFileTlr.Length +
          info.Length;
        System.IO.Stream io;
        io = getStream();
        writeString(io, strFields);
        writeString(io, strFileHdr);
        this.writeFile(io, aFilename);
        writeString(io, strFileTlr);
        getResponse();
        io.Close();
        // End the life time of this request object. 
        coRequest = null;
    }
    /// <summary> 
    /// Mainly used to turn the string into a byte buffer and then 
    /// write it to our IO stream. 
    /// </summary> 
    /// <param name="io">The io stream for output.</param> 
    /// <param name="str">The data to write.</param> 
    public void writeString(System.IO.Stream io, string str)
    {
        byte[] PostData = System.Text.Encoding.ASCII.GetBytes(str);
        io.Write(PostData, 0, PostData.Length);
    }
    /// <summary> 
    /// Builds the proper format of the multipart data that 
    /// contains the form fields and their respective values. 
    /// </summary> 
    /// <returns>The data to send in the multipart upload.</returns> 
    public string getFormfields()
    {
        string str = "";
        IDictionaryEnumerator myEnumerator = coFormFields.GetEnumerator();
        while (myEnumerator.MoveNext())
        {
            str += ContentBoundary + "\r\n" +
              CONTENT_DISP + '"' + myEnumerator.Key + "\"\r\n\r\n" +
              myEnumerator.Value + "\r\n";
        }
        return str;
    }
    /// <summary> 
    /// Returns the proper content information for the 
    /// file we are sending. 
    /// </summary> 
    /// <remarks> 
    /// Hits Patel reported a bug when used with ActiveFile. 
    /// Added semicolon after sendfile to resolve that issue. 
    /// Tested for compatibility with IIS 5.0 and Java. 
    /// </remarks> 
    /// <param name="aFilename"></param> 
    /// <returns></returns> 
    public string getFileheader(string aFilename)
    {
        return ContentBoundary + "\r\n" +
          CONTENT_DISP +
          "\"" + InputBoxName.ToString() + "\"; filename=\"" +
          Path.GetFileName(aFilename) + "\"\r\n" +
          "Content-type: " + FileContentType + "\r\n\r\n";
    }
    /// <summary> 
    /// Creates the proper ending boundary for the multipart upload. 
    /// </summary> 
    /// <returns>The ending boundary.</returns> 
    public string getFiletrailer()
    {
        return "\r\n" + EndingBoundary;
    }
    /// <summary> 
    /// Reads in the file a chunck at a time then sends it to the 
    /// output stream. 
    /// </summary> 
    /// <param name="io">The io stream to write the file to.</param> 
    /// <param name="aFilename">The name of the file to transfer.</param> 
    public void writeFile(System.IO.Stream io, string aFilename)
    {
        FileStream readIn = new FileStream(aFilename, FileMode.Open, FileAccess.Read);
        readIn.Seek(0, SeekOrigin.Begin); // move to the start of the file 
        byte[] fileData = new byte[BufferSize];
        int bytes;
        while ((bytes = readIn.Read(fileData, 0, BufferSize)) > 0)
        {
            // read the file data and send a chunk at a time 
            io.Write(fileData, 0, bytes);
        }
        readIn.Close();
    }
}

